/*
 *  Copyright 2012 Iurii Dunko, Iana Potochniak.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.yyminijava.codegeneration;

import org.opu.yyminijava.util.CodeGenerationException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.opu.yyminijava.symboltable.SymbolTable;
import org.opu.yyminijava.symboltable.VarRecord;
import org.opu.yyminijava.syntaxtree.*;
import org.opu.yyminijava.syntaxtree.primary.Primary;
import org.opu.yyminijava.syntaxtree.primary.PrimaryFromExpression;
import org.opu.yyminijava.syntaxtree.primary.PrimaryFromIdentifier;
import org.opu.yyminijava.syntaxtree.primary.PrimaryFromLiteral;
import org.opu.yyminijava.syntaxtree.primary.PrimaryFromNewInstance;
import org.opu.yyminijava.syntaxtree.primary.PrimaryFromThis;
import org.opu.yyminijava.syntaxtree.primary.PrimaryMethodCall;
import org.opu.yyminijava.util.MathUtil;
import org.opu.yyminijava.util.MathUtil.PostfixNotationElement;
import org.opu.yyminijava.util.MathUtil.PostfixNotationType;
import org.opu.yyminijava.visitor.TypeCheckingHelper;

/**
 *
 * @author Iurii Dunko
 * @author Iana Potochniak
 */
public class ExpressionCodegenerationHelper {

    private SymbolTable symbolTable;
    private TypeCheckingHelper helper;
    private List<String> methodCalls = new ArrayList<String>();
    
	public ExpressionCodegenerationHelper(SymbolTable symbolTable) {
		this.symbolTable = symbolTable;
        this.helper = new TypeCheckingHelper(symbolTable, "!");
	}

	public void parseExpression(Expression exp, Method method){
		PostfixNotationElement[] arr = MathUtil.toPostfixNotation(exp);
		for (PostfixNotationElement elem : arr) {
			if (elem.type == PostfixNotationType.POSTFIX_EXPRESSION){
				parsePostfixExpression((PostfixExpression)elem.value, method);
			} else if (elem.type == PostfixNotationType.MATH_OPERATION){
				if (elem.value instanceof BooleanOp){
					if (BooleanOp.XAND.equals(elem.value)){
						method.addInstruction(ICode.IAND, null);
					} else if (BooleanOp.XOR.equals(elem.value)){
						method.addInstruction(ICode.IOR, null);
					} else {
						faile("Unsuported bool op: '" + elem.value + "' (" + exp + ")");
					}
				} else if (elem.value instanceof LogicalOp){
					if (LogicalOp.EQUAL.equals(elem.value)){
						method.addInstruction(ICode.IEQUAL, null);
					} else if (LogicalOp.NOT_EQUAL.equals(elem.value)){
						method.addInstruction(ICode.INOT_EQUAL, null);
					} else {
						faile("Unsuported logical op: '" + elem.value + "' (" + exp + ")");
					}
				} else if (elem.value instanceof CompareOp){
					if (CompareOp.LESS.equals(elem.value)){
						method.addInstruction(ICode.ILT, null);
					} else if (CompareOp.BIGGER.equals(elem.value)){
						method.addInstruction(ICode.IGT, null);
					} else {
						faile("Unsuported complare op: '" + elem.value + "' (" + exp + ")");
					}
				} else if (elem.value instanceof ArithmeticalAddOp){
					if (ArithmeticalAddOp.MINUS.equals(elem.value)){
						method.addInstruction(ICode.ISUB, null);
					} else if (ArithmeticalAddOp.PLUS.equals(elem.value)){
						method.addInstruction(ICode.IADD, null);
					} else {
						faile("Unsuported +/- op: '" + elem.value + "' (" + exp + ")");
					}
				} else if (elem.value instanceof ArithmeticalMullOp){
					if (ArithmeticalMullOp.MULTIPLICATION.equals(elem.value)){
						method.addInstruction(ICode.IMUL, null);
					} else if (ArithmeticalMullOp.DIVISION.equals(elem.value)){
						method.addInstruction(ICode.IDIV, null);
					} else {
						faile("Unsuported '*'/'/' op: '" + elem.value + "' (" + exp + ")");
					}
				} else if (elem.value instanceof PrefixOp){
					if (PrefixOp.DENIAL.equals(elem.value)){
						method.addInstruction(ICode.INOT, null);
					} else {
						faile("Unsuported prefix op: '" + elem.value + "' (" + exp + ")");
					}
				}
			} else {
				faile("Unsuported postfix element type: '" + elem.type + "' (" + exp + ")");
			}
		}
    }
	
	public int getVarIndex(PostfixExpression pexp, Method method, String errM){
		Primary primary = pexp.getPrimary();
		
		if (primary instanceof PrimaryFromIdentifier){
			String name = ((PrimaryFromIdentifier) primary).getSimpleName();
			
			if(symbolTableContaineVar(name)) {
				faile(!pexp.getSuffixs().isEmpty(), 
						"Suffix in this situation is imposible: " + errM);
                return method.indexOf(name);
            }
		}
		
		faile("Can't be applied not to variable: " + errM);
		return -1;
	}

	public void parsePostfixExpression(PostfixExpression pexp, Method method){
		Primary primary = pexp.getPrimary();
        List<Suffix> suffixs = pexp.getSuffixs();
		
        if (primary instanceof PrimaryFromLiteral) {
			Literal<?> literal = ((PrimaryFromLiteral) primary).getLiteral();
			if (literal instanceof BooleanLiteral){
				putConst(method, ((BooleanLiteral)literal).getLiteralValue()? 1: 0);
			} else if(literal instanceof IntegerLiteral){
				putConst(method, ((IntegerLiteral)literal).getLiteralValue());
			} else {
				faile("Available primitive only of simple types (int, boolean): " + pexp);
			} 
		}
		
        else if (primary instanceof PrimaryFromIdentifier){
			String name = ((PrimaryFromIdentifier) primary).getSimpleName();
            
			if(symbolTableContaineVar(name)) {
				// 1st situation - it is call of simple variable: 
				//   - we put it call into instruction list
				//   - we check that we don't have suffix
                method.addInstruction(ICode.ILOAD, method.indexOf(name));
				faile(!suffixs.isEmpty(), "Suffix in this situation is not implemented: " + pexp);
                return;
            } else if (symbolTableContaineClass(name)){
            	// 2nd situation - it is call of class (static method call)
//            	System.err.println("WARNING: Static method call: " + pexp);
                qualifiMethodCall(pexp, method, true, suffixs, name);
            } else {
            	faile("Bad primitive from identifier: " + pexp); 
            }
		} 
		
        else if (primary instanceof PrimaryFromExpression) {
			// faile("Creation primitives from another expression not implemented: " + pexp);
//			System.err.println("WARNING: Expression in expression: " + pexp);
			parseExpression(((PrimaryFromExpression)primary).getExpression(), method);
		} 
		
        else if (primary instanceof PrimaryFromNewInstance) {
			CreationExpression ce = (((PrimaryFromNewInstance) primary)
					.getNewInstance()).getCreationExpression();
			
			if (ce instanceof ClassInstanceCreationExpression) {
				String name = ((ClassInstanceCreationExpression)ce)
						.getClassType().getName();
				qualifiMethodCall(pexp, method, false, suffixs, name);
			} else {
				faile("Now implementation for call: " + pexp);
			}
		} 

        else if (primary instanceof PrimaryFromThis){
        	String name = symbolTable.getThis().getName();
        	qualifiMethodCall(pexp, method, false, suffixs, name);
        }

        else if (primary instanceof PrimaryMethodCall){
    		StringBuilder n = new StringBuilder();// method qName
    		n.append(symbolTable.getThis().getName());
    		MethodCall m = ((PrimaryMethodCall)primary).getMethodCall();
    		methodCall(pexp, method, false, n, Collections.emptyList().iterator(), m);
        } else {
        	faile("Now implementation for call: " + pexp);
        }
    	
    }
	
	public List<String> resetStaticMethodCalls(){
		ArrayList<String> arrayList = new ArrayList<String>(methodCalls);
		methodCalls.clear();
		return arrayList;
	}

	private void qualifiMethodCall(PostfixExpression pexp, Method method,
			boolean st, List<Suffix> suffixs, String name) {
		StringBuilder mqn = new StringBuilder();// method qName
		mqn.append(name);
		
		for (Iterator<Suffix> itr = suffixs.iterator(); itr.hasNext();) {
			Suffix suffix = (Suffix) itr.next();
			if (suffix instanceof FieldInvocation) {
                // FIXME: Yuriy Dunko: It is crutches for "System.out.print"
				mqn.append('.').append(((FieldInvocation)suffix).getSimpleName());
			} else if(suffix instanceof MethodInvocation){
				MethodCall methodCall = ((MethodInvocation)suffix).getMethodCall();
				methodCall(pexp, method, st, mqn, itr, methodCall);
				return; // we successfully created method call. 
			}
		}
		
		// it's can happen if we don't found method call at the end
		faile("To complex expression: " + pexp);
	}

	private void methodCall(PostfixExpression pexp, Method method, boolean st,
			StringBuilder mqn, Iterator<?> itr, MethodCall methodCall) {
		mqn.append('.').append(methodCall.getSimpleName());
		
		if(itr.hasNext()){
			faile("To complex expression: " + pexp);
		}
		
		ArgumentList args = methodCall.getArgumentList();
		if(args != null){
            // We must put input data in reverse order (stack ...)
			for(Expression exp: revert(args.getExpressions())){
                // If it argument it must be 
                Type type = helper.confirmType(exp);
                faile(type == null, "void type argument: " + pexp);
				parseExpression(exp, method);
			}
		}
		
		if (st) {
			methodCalls.add(mqn.toString());
		}
		
		method.addInstruction(ICode.INVOKEVIRTUAL, mqn.toString());
	}
	
	private void putConst(Method method, int value){
		method.addInstruction(ICode.ICONST, value);
	}
	
	private boolean symbolTableContaineVar(String varName){
		try {
            VarRecord lookup = symbolTable.lookup(new VarRecord(varName));
            return lookup != null;
        } catch (Exception e) {
        	return false;
        }
	}
	
	private boolean symbolTableContaineClass(String className){
		try {
			symbolTable.getClass(className);
            return true;
        } catch (Exception e) {
        	return false;
        }
	}
	
	private void faile(boolean b, String msg){
		if (b){
			faile(msg);
		}
	}
	
	private void faile(String msg) throws CodeGenerationException{
		throw new CodeGenerationException(msg);
	}

    private <E> Collection<E> revert(Collection<E> in){
        ArrayList<E> list = new ArrayList<E>(in);
        Collections.reverse(list);
        return list;
    }
}
